package com.crowd.web.config;

import com.crowd.constant.CrowdConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author Stone
 * @date 2020/6/2 16:24
 * springSecurity配置类
 */
@Configuration
@EnableWebSecurity
// 启用全局方法权限控制功能，并且设置prePostEnabled = true。保证@PreAuthority、
// @PostAuthority、@PreFilter、@PostFilter 生效
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CrowdfundingSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private CrowdUserDetailsService userDetailsService;
    // 在这里不用@Bean将BCryptPasswordEncoder进行注入
    // 因为添加用户的时候，也需要加密，是service模块用的
    // 如果在这里注入，bean在springMVC的IOC容器，service在springIOC容器，这样会找不到bean
    // 所以我们在spring-context用xml里面的bean标签注入
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    @Override
    protected void configure(HttpSecurity security) throws Exception {
//        super.configure(http);
        security.authorizeRequests()
                .antMatchers("/admin/to/login/page.html")               // 匹配到登录的url时
                .permitAll()                                                        // 放行
                .antMatchers("/bootstrap/**","/crowjs/**","/css/**",
                        "/fonts/**","/img/**","/jquery/**","/layer/**",
                        "/script/**","/ztree/**")                                   // 匹配到静态资源时
                .permitAll()                                                        // 放行
                .antMatchers("/admin/get/page.html")                    // 匹配到用户查看页时
                // 第一种方式验证
                // 这种方式压根就不会进controller，所以我们的自定义异常CrowdExceptionResolver没用
                // 所以我们需要在security里面写异常处理方法exceptionHandling()
                // 第二种方式是在controller中加注解，千万别忘了，在config类也要加注解
                //.hasRole("用户菜单管理员")                                            // 需要拥有角色"用户菜单管理员"才允许访问
                // 第一种方式 同時需要角色和权限时
                .access("hasRole('用户菜单管理员') OR hasAuthority('user:get')") // 需要拥有角色"用户菜单管理员"或者权限user:get才允许访问
                .anyRequest()                                                       // 其他任何请求
                .authenticated()                                                    // 都需要先通过验证
                .and()
                .exceptionHandling()                                                // 异常处理
                .accessDeniedHandler(new AccessDeniedHandler() {                    // 拒绝访问时，我们跳转到error.jsp
                    @Override
                    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
                        httpServletRequest.setAttribute(CrowdConstant.ATTR_NAME_EXCEPTION, new Exception(CrowdConstant.MESSAGE_ACCESS_DENIED));
                        httpServletRequest.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(httpServletRequest, httpServletResponse);
                    }
                })
                .and()
                .csrf()                                                             // csrf 跨域请求
                .disable()                                                          // 禁用
                .formLogin()                                                        // 登录为，表单提交模式。因为关闭了csrf，不需要携带token属性在表单
                .loginPage("/admin/to/login/page.html")                             //
                .loginProcessingUrl("/security/do/login.html")                      // 指定处理登录请求的地址
                // defaultSuccessUrl()失效时，改方法为successForwardUrl（）
                // 但是这个url一定要是post请求
                // 要在controller改成post请求
                .successForwardUrl("/admin/to/main/page.html")                      // 登录成功后返回页面
                .usernameParameter("loginAcct")                                     // 账号的请求参数名称
                .passwordParameter("userPswd")                                      // 密码的请求参数名称
                .and()
                .logout()
                .logoutUrl("/seucrity/do/logout.html")                              // 指定处理登出请求的地址
                .logoutSuccessUrl("/admin/to/login/page.html")                      // 指定处理登出请求的页面
                ;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
//        super.configure(auth);
//        builder.inMemoryAuthentication()
//                .withUser("stone").password("123456")
//                .roles("admin");

        builder.userDetailsService(userDetailsService)
                // 使用盐值匹配
               .passwordEncoder(bCryptPasswordEncoder)
                ;
    }
}
